package top.rish.universe.core.plugin;

import cn.hutool.core.util.StrUtil;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.noear.socketd.SocketD;
import org.noear.socketd.transport.core.Message;
import org.noear.socketd.transport.core.Session;
import org.noear.socketd.transport.core.entity.StringEntity;
import org.noear.socketd.transport.core.listener.SimpleListener;
import org.noear.solon.Solon;
import org.noear.solon.Utils;
import org.noear.solon.core.ExtendLoader;
import org.noear.solon.core.event.EventBus;
import org.noear.solon.core.util.LogUtil;
import org.noear.solon.core.util.ResourceUtil;
import org.noear.solon.core.util.ScanUtil;
import top.rish.universe.core.exception.PluginDependencyException;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public   class UniPluginUtil {


    public static void scanPlugins(ClassLoader classLoader, Consumer<UniversePluginEntity> consumer) {
        Collection<String> nameList = ScanUtil.scan(classLoader, "META-INF/universe", n -> n.endsWith(".yml"));
        for (String name : nameList) {
            URL res = ResourceUtil.getResource(classLoader, name);
            if (res == null) {
                // native 时，扫描出来的resource可能是不存在的（这种情况就是bug），需要给于用户提示，反馈给社区
                LogUtil.global().warn("Solon plugin: name=" + name + ", resource is null");
            } else {
                Properties props = Utils.loadProperties(res);
                String classFilePath = null;
                if("file".equals(res.getProtocol())){
                    classFilePath = res.toString().replaceAll("^file:/","").replaceAll("META-INF/universe.*?$","");
                }
                UniPluginUtil.findPlugins(classLoader,classFilePath,  props, consumer);
            }
        }
    }

    public static void checkDependenciesAndByPriorityAdd(
            List<UniversePluginEntity> plugins
    ) {
        plugins.stream()
                .peek(it -> {
                    it.setPriority(flowPriorityOfDependency(it, plugins));
                })
                .forEach(Solon.cfg().plugs()::add);

    }

    private static int flowPriorityOfDependency(UniversePluginEntity plugin,
                                                List<UniversePluginEntity> all) {
        if (Utils.isNotEmpty(plugin.getDependencies())) {
            plugin.getDependencies().stream().map(dependency -> {
                Optional<UniversePluginEntity> matchDependency =
                all.stream().filter(parent -> Objects.equals(parent.getId(), dependency.getId())
                        && Objects.equals(parent.getVersion(), dependency.getVersion())).findFirst();
                if (matchDependency.isPresent()) {
                    return matchDependency.get().getPriority();
                } else {
                    throw new PluginDependencyException(
                    StrUtil.format("plugin:{}@{} not match dependency:{}@{}",
                    plugin.getId(), plugin.getVersion(),
                    dependency.getId(), dependency.getVersion()));
                }
            }).min(Integer::compareTo)
            .ifPresent(priority->{
                plugin.setPriority(priority-1);
            });
        }
        return Integer.MAX_VALUE;
    }


    public static void findPlugins(ClassLoader classLoader,String  classFilePath, Properties props,
                                   Consumer<UniversePluginEntity> consumer) {
        String pluginClass = props.getProperty("plugin.class");
        String id = props.getProperty("plugin.id");
        String name = props.getProperty("plugin.name");
        String version = props.getProperty("plugin.version");
        String author = props.getProperty("plugin.author");
        String domain = props.getProperty("plugin.domain");
        String description = props.getProperty("plugin.description");
        String url = props.getProperty("plugin.url");
        String dependenciesStr = props.getProperty("plugin.dependencies");
        if (Utils.isNotEmpty(pluginClass)) {
            String priorityStr = props.getProperty("plugin.priority");
            int priority = Integer.MAX_VALUE;
            if (Utils.isNotEmpty(priorityStr)) {
                priority = Integer.parseInt(priorityStr);
            }
            UniversePluginEntity ent = new UniversePluginEntity(classLoader, pluginClass.trim(), props);
            ent.setPriority(priority);
            ent.setId(id);
            ent.setName(name);
            ent.setVersion(version);
            ent.setDescription(description);
            ent.setAuthor(author);
            ent.setUrl(url);
            ent.setDomain(domain);
            ent.setProperties(props);
            ent.setClassFilePath(classFilePath);
            if (Utils.isNotEmpty(dependenciesStr)) {
                List<UniPluginEntityDependency> dependencies =
                        Arrays.stream(dependenciesStr.split(","))
                                .filter(it -> it.contains(":"))
                                .map(it -> {
                                    String[] split = it.split(":");
                                    if (split.length == 2
                                            && Utils.isNotEmpty(split[0])
                                            && Utils.isNotEmpty(split[1])) {
                                        return UniPluginEntityDependency.builder()
                                                .id(split[0])
                                                .version(split[1])
                                                .build();
                                    } else {
                                        throw new PluginDependencyException("plugin.dependencies config error");
                                    }
                                })
                                .filter(Objects::nonNull)
                                .collect(Collectors.toList());
                if (Utils.isNotEmpty(dependencies)) {
                    ent.setDependencies(dependencies);
                }
            }
            consumer.accept(ent);
        }else{
            LogUtil.global().warn(StrUtil.format("Universe {}@{} plugin.class is empty!!!", id, version));
        }
    }

    public static void loadUniversePlugins() {
        LogUtil.global().info("Universe plugins starting...");
        List<ClassLoader> classLoaders  = ExtendLoader.load(Solon.cfg().extend(), false);;
        for (ClassLoader classLoader : classLoaders) {
            UniPluginUtil.scanPlugins(classLoader, UniversePlugin.plugins::add);
        }
        UniPluginUtil.checkDependenciesAndByPriorityAdd(UniversePlugin.plugins);
        if (!UniversePlugin.plugins.isEmpty()) {
            Collections.sort(UniversePlugin.plugins);
        }
        for (UniversePluginEntity uniPlugin : UniversePlugin.plugins) {
            uniPlugin.init(Solon.context());
            uniPlugin.start(Solon.context());
            watchClassPathAndHotSwap(uniPlugin);
        }
        LogUtil.global().info("Universe plugins loaded");
    }

    /**
     * 监听插件的类路径所有文件是否更改，并热更新
     * @param uniPlugin
     */
    private static void watchClassPathAndHotSwap(UniversePluginEntity uniPlugin) {
        String hotPath = uniPlugin.getClassFilePath();
        if(hotPath==null){
            return;
        }
        String path = hotPath.replaceAll("^file:/","");
        LogUtil.global().info(path+" is be observer");
        long interval = TimeUnit.SECONDS.toMillis(1);
        FileAlterationObserver observer = new FileAlterationObserver(path);
        observer.addListener(new PluginClassFilesListener(uniPlugin));
        FileAlterationMonitor monitor = new FileAlterationMonitor(interval,observer);
        // 开始监控
        try {
            monitor.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}
